security: reject code-bearing snapshot fields (execute/domTransformation) from the local API (PER-8607, PER-8613)#2280
Conversation
…-8607, PER-8613) The local /percy/snapshot endpoint is unauthenticated, so accepting `execute` (run via page.eval) and `domTransformation` (passed to window.eval) from a network request body lets any local caller inject arbitrary JavaScript into the — possibly authenticated — page being snapshotted (CWE-94, CVSS 8.8). Add stripRemoteScriptFields(): the POST /percy/snapshot handler now strips `execute` and `domTransformation` (recursively, incl. additionalSnapshots[]) from the request body before it reaches percy.snapshot(), logging a warning when it does. The config-file / CLI path (`percy snapshot`) calls percy.snapshot() directly and never traverses this route, so legitimate config-sourced execute/domTransformation continue to work. Trusted programmatic callers can opt back in with PERCY_ALLOW_REMOTE_SCRIPTS=true. Mirrors the existing stripBlockedConfigFields control on /percy/config. Adds unit tests covering removal, nested removal, the opt-in override, and non-object bodies. NOTE: this changes default behaviour for any caller that POSTs a URL snapshot with execute/domTransformation to the local API expecting server-side execution — those fields are now ignored unless the opt-in env is set. Flagged for review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude Code PR ReviewPR: #2280 • Head: 3706fa1 • Reviewers: stack:code-reviewer SummaryAdds Review Table
Findings
Verdict: PASS |
Summary
Third focused percy-cli security PR — two High-severity code-injection findings (CVSS 8.8, deadline 2026-06-16).
domTransformationstring from POST body reacheswindow.evalin Chromiumexecutehooks)Root cause
The local
/percy/snapshotendpoint is unauthenticated.execute(run viapage.eval) anddomTransformation(passed towindow.evalin@percy/dom) flow straight fromreq.bodyinto the browser context of the page being snapshotted. Since Percy often snapshots authenticated/staging pages, an injected script can exfiltrate cookies/tokens or SSRF internal services.Fix
Added
stripRemoteScriptFields()and wired it into thePOST /percy/snapshothandler:executeanddomTransformationare removed (recursively, includingadditionalSnapshots[]) from the network request body before it reachespercy.snapshot(), with a warning logged.Why this is safe for legitimate use: the config-file / CLI path (
percy snapshot <url|sitemap>) callspercy.snapshot()directly and never passes through this HTTP route (confirmed inpackages/cli-snapshot/src/snapshot.js→percy.yield.snapshot(options)), so config-sourcedexecute/domTransformationare unaffected. This mirrors the existingstripBlockedConfigFieldscontrol already applied to/percy/config.Trusted programmatic callers that intentionally POST a URL +
execute/domTransformationto the local API can opt back in withPERCY_ALLOW_REMOTE_SCRIPTS=true.Verification
node --checkpasses; logic verified against the ticket's exploit payloads (top-level + nestedexecute/domTransformationremoved;url/name/additionalSnapshots[].namepreserved; original body not mutated; opt-in retains fields).api.test.js(describe('stripRemoteScriptFields')): removal, no-op + no-warn on clean bodies, opt-in override, non-object bodies.Closes PER-8607, PER-8613.
🤖 Generated with Claude Code